/*
 * Copyright 2011 Raimon Rodriguez
 *
 * This file is part of sgfx.
 *
 * sgfx is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * sgfx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with sgfx. If not, see <http://www.gnu.org/licenses/>.
 */

package com.googlecode.sgfx.controller;

import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.googlecode.sgfx.base.Keys;
import com.googlecode.sgfx.base.Log;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.swing.DropMode;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

// Should handle the case where at least one JList is used in two different double list
// controller builders?

// TODO: meta down key for macs, ctrl down for other systems for default key bindings.
// TODO: allow DnD icon customization.

/**
 * A builder to create instances of {@code DoubleListController}.
 * 
 * Assumption: all elements in both lists are of the same type.
 * 
 * Note: The list models of both {@code JList} instances are replaced with others, transferring all
 * the elements to the new ones. However, previously registered {@code ListDataListener}s won't be
 * migrated so it's highly advisable that any custom {@code ListDataListener} be registered after
 * the attach method is invoked.
 *
 * Instances cannot be reused after {@link #attach()} is invoked.
 * Instances of {@code DoubleListControllerBuilder} are not thread safe.
 * 
 * @author raimon.rodriguez
 */
public final class DoubleListControllerBuilder<E> {
    private static final Log log = Log.getLog(DoubleListControllerBuilder.class);
    private static final int NUMBER_OF_ACTIONS;
    private static final MoveControls<KeyStroke> SOURCE_LIST_DEFAULT_KEY_STROKES;
    private static final MoveControls<KeyStroke> DEST_LIST_DEFAULT_KEY_STROKES;

    private ListWidgetAndMoveControls<E> srcListAndMoveControls;
    private ListWidgetAndMoveControls<E> destListAndMoveControls;
    private Set<JButton> buttons;
    private boolean defaultKeyBindingsEnabled;
    private boolean dndEnabled;
    private DoubleListController controller;

    static {
        NUMBER_OF_ACTIONS = 4;
        SOURCE_LIST_DEFAULT_KEY_STROKES = new MoveControls<KeyStroke>(
                Keys.stroke(KeyEvent.VK_RIGHT),
                KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.META_DOWN_MASK));
        DEST_LIST_DEFAULT_KEY_STROKES = new MoveControls<KeyStroke>(
                Keys.stroke(KeyEvent.VK_LEFT),
                KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.META_DOWN_MASK));
    }


    /**
     * Constructs a double list controller builder with the specified source and destination
     * lists.
     *
     * @param pSourceList the source list.
     * @param pDestList the destination list.
     * @throws NullPointerException if any of the parameters is null.
     * @throws IllegalArgumentException if the source and destination lists reference the same
     * instance
     */
    public DoubleListControllerBuilder(JList pSourceList, JList pDestList) {
        Preconditions.checkNotNull(pSourceList, "pSourceList");
        Preconditions.checkNotNull(pDestList, "pDestList");
        Preconditions.checkArgument(pSourceList != pDestList, "source and destination lists "
                + "reference the same instance");

        srcListAndMoveControls = new ListWidgetAndMoveControls<E>(pSourceList, SOURCE_LIST_DEFAULT_KEY_STROKES);
        destListAndMoveControls = new ListWidgetAndMoveControls<E>(pDestList, DEST_LIST_DEFAULT_KEY_STROKES);
        buttons = new HashSet<JButton>(NUMBER_OF_ACTIONS);
    }


    /**
     * Sets the button to perform the "add" action, i.e. move selected item(s) from the source
     * to the destination list. An exception is thrown if the specified button is already bound
     * to another action. Passing null has the effect of unbinding any previous button set
     * for this action.
     * 
     * @param pButton the button to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same button is already assigned to another action.
     */
    public DoubleListControllerBuilder<E> setAddButton(JButton pButton) {
        srcListAndMoveControls.setMoveSelectedButton(setButtonHelper(
                srcListAndMoveControls.getMoveSelectedButton(), pButton));
        return this;
    }

    /**
     * Sets the button to perform the "remove" action, i.e. move selected item(s) from the 
     * destination to the source list. An exception is thrown if the specified button is already
     * bound to another action. Passing null has the effect of unbinding any previous button set
     * for this action.
     *
     * @param pButton the button to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same button is already assigned to another action.
     */
    public DoubleListControllerBuilder<E> setRemoveButton(JButton pButton) {
        destListAndMoveControls.setMoveSelectedButton(
                setButtonHelper(destListAndMoveControls.getMoveSelectedButton(), pButton));
        return this;
    }

    /**
     * Sets the button to perform the "add all" action, i.e. move all items from the source
     * to the destination list. An exception is thrown if the specified button is already bound
     * to another action. Passing null has the effect of unbinding any previous button set
     * for this action.
     *
     * @param pButton the button to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same button is already assigned to another action.
     */
    public DoubleListControllerBuilder<E> setAddAllButton(JButton pButton) {
        srcListAndMoveControls.setMoveAllButton(
                setButtonHelper(srcListAndMoveControls.getMoveAllButton(), pButton));
        return this;
    }

    /**
     * Sets the button to perform the "remove all" action, i.e. move all items from the destination
     * to the source list. An exception is thrown if the specified button is already bound
     * to another action. Passing null has the effect of unbinding any previous button set
     * for this action.
     *
     * @param pButton the button to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same button is already assigned to another action.
     */
    public DoubleListControllerBuilder<E> setRemoveAllButton(JButton pButton) {
        destListAndMoveControls.setMoveAllButton(
                setButtonHelper(destListAndMoveControls.getMoveAllButton(), pButton));
        return this;
    }

    /**
     * Sets the specified key stroke to perform the "add" action, i.e. move selected item(s) from
     * the source to the destination list. An exception is thrown if the specified key stroke is
     * already bound to the "add all" action. Passing null has the effect of unbinding any previous
     * key stroke set for this action.
     * 
     * @param pKeyStroke the key stroke to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same key stroke is already assigned to the "add all"
     * action.
     */
    public DoubleListControllerBuilder<E> setAddKeyStroke(KeyStroke pKeyStroke) {
        srcListAndMoveControls.setMoveSelectedKey(pKeyStroke);
        return this;
    }

    /**
     * Sets the specified key stroke to perform the "remove" action, i.e. move selected item(s) from
     * the destination to the source list. An exception is thrown if the specified key stroke is
     * already bound to the "remove all" action. Passing null has the effect of unbinding any 
     * previous key stroke set for this action.
     *
     * @param pKeyStroke the key stroke to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same key stroke is already assigned to the "remove
     * all" action.
     */
    public DoubleListControllerBuilder<E> setRemoveKeyStroke(KeyStroke pKeyStroke) {
        destListAndMoveControls.setMoveSelectedKey(pKeyStroke);
        return this;
    }

    /**
     * Sets the specified key stroke to perform the "add all" action, i.e. move all items from
     * the source to the destination list. An exception is thrown if the specified key stroke is
     * already bound to the "add" action. Passing null has the effect of unbinding any previous
     * key stroke set for this action.
     *
     * @param pKeyStroke the key stroke to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same key stroke is already assigned to the "add"
     * action.
     */
    public DoubleListControllerBuilder<E> setAddAllKeyStroke(KeyStroke pKeyStroke) {
        srcListAndMoveControls.setMoveAllKey(pKeyStroke);
        return this;
    }

    /**
     * Sets the specified key stroke to perform the "remove all" action, i.e. move all items from
     * the destination to the source list. An exception is thrown if the specified key stroke is
     * already bound to the "remove" action. Passing null has the effect of unbinding any previous
     * key stroke set for this action.
     *
     * @param pKeyStroke the key stroke to be bound to this action.
     * @return a reference to this object.
     * @throws IllegalArgumentException if the same key stroke is already assigned to the "remove"
     * action.
     */
    public DoubleListControllerBuilder<E> setRemoveAllKeyStroke(KeyStroke pKeyStroke) {
        destListAndMoveControls.setMoveAllKey(pKeyStroke);
        return this;
    }

    /**
     * Enables default key bindings for moving items between lists. This property will be completely
     * ignored in the attach process if any custom key stroke is set.
     * 
     * @return a reference to this object.
     */
    public DoubleListControllerBuilder<E> enableDefaultKeyBindings() {
        defaultKeyBindingsEnabled = true;
        return this;
    }

    /**
     * Enables drag and drop to move selected items between source and destination list
     * and vice versa.
     * 
     * @return a reference to this object.
     */
    public DoubleListControllerBuilder<E> enableDragAndDrop() {
        dndEnabled = true;
        return this;
    }

    /**
     * Creates the double list controller and attaches it to all the components as specified in this
     * builder, so as to enable the moving of items between lists. Calling this method more than
     * once returns the same double list controller instance every time, unmodified, even if any
     * of the build steps has been modified.
     * <b>Warning:</b> This method should be invoked inside the EDT if any of the components have
     * been already realized.
     * 
     * @return the double list controller that is attached to the components as specified in this
     * builder.
     */
    public DoubleListController attach() {
        if (!SwingUtilities.isEventDispatchThread()) {
            log.warning("Attaching outside EDT might cause problems if components have been realized.");
        }

        if (controller != null) return controller;
        
        DoubleListController tController = new DoubleListController();

        if (!isCustomKeysEmpty() || !buttons.isEmpty() || defaultKeyBindingsEnabled || dndEnabled) {
            attachHelper(srcListAndMoveControls, destListAndMoveControls.getSelectionEventList(), tController);
            attachHelper(destListAndMoveControls, srcListAndMoveControls.getSelectionEventList(), tController);
        }

        log.fine("Total of %d listeners registered", tController.getRegistrations().size());
        
        controller = tController;
        return controller;
    }
    
    private JButton setButtonHelper(JButton pOldButton, JButton pNewButton) {
        if (buttons.contains(pNewButton) && pOldButton != pNewButton) {
            throw new IllegalArgumentException("Button already assigned to another action");
        } else if (pNewButton == null) {
            buttons.remove(pOldButton);
        } else {
            buttons.add(pNewButton);
        }

        return pNewButton;
    }

    private void attachHelper(ListWidgetAndMoveControls<E> pSrcListAndMovers,
            SelectionEventList<E> pDestList, DoubleListController pController) {

        SelectionEventList<E> tSrcEventList = pSrcListAndMovers.getSelectionEventList();
        ItemsMover<E> tItemsMover = new ItemsMover<E>(tSrcEventList, pDestList);

        if (pSrcListAndMovers.isButtonMoversNotEmpty()) {
            ButtonsEnablerController<E> tButtonsController = new ButtonsEnablerController<E>();
            pController.register(tSrcEventList, (ListEventListener<E>) tButtonsController);

            JButton tMoveSelectedButton = pSrcListAndMovers.getMoveSelectedButton();
            if (tMoveSelectedButton != null) {
                pController.register(
                        tMoveSelectedButton, new MoveSelectedActionListener<E>(tItemsMover));
                tMoveSelectedButton.setEnabled(!tSrcEventList.isSelectionEmpty());
                tButtonsController.setMoveSelectedButton(tMoveSelectedButton);
                pController.register(tSrcEventList, (ListSelectionListener) tButtonsController);
            }

            if (pSrcListAndMovers.getMoveAllButton() != null) {
                pController.register(pSrcListAndMovers.getMoveAllButton(),
                        new MoveAllActionListener<E>(tItemsMover));
                pSrcListAndMovers.getMoveAllButton().setEnabled(!tSrcEventList.isEmpty());
                tButtonsController.setMoveAllButton(pSrcListAndMovers.getMoveAllButton());
            }
        }

        boolean tDefaultKeyBindingAllowed = defaultKeyBindingsEnabled && isCustomKeysEmpty();
        KeyStroke tMoveSelectedKey = tDefaultKeyBindingAllowed?
                pSrcListAndMovers.getDefaultMoveSelectedKey() : pSrcListAndMovers.getMoveSelectedKey();
        if (tMoveSelectedKey != null) {
            pController.register(pSrcListAndMovers.listWidget,
                        new MoveSelectedKeyListener<E>(tItemsMover, tMoveSelectedKey));
        }

        KeyStroke tMoveAllKey = tDefaultKeyBindingAllowed?
                pSrcListAndMovers.getDefaultMoveAllKey() : pSrcListAndMovers.getMoveAllKey();
        if (tMoveAllKey != null) {
            pController.register(pSrcListAndMovers.listWidget,
                    new MoveAllKeyListener<E>(tItemsMover, tMoveAllKey));
        }

        if (dndEnabled) {
            pSrcListAndMovers.listWidget.setTransferHandler(new ItemsTransferHandler<E>(tItemsMover));
            pSrcListAndMovers.listWidget.setDragEnabled(true);
            pSrcListAndMovers.listWidget.setDropMode(DropMode.ON_OR_INSERT);
        }
    }

    private boolean isCustomKeysEmpty() {
        return srcListAndMoveControls.isKeyMoversEmpty() && destListAndMoveControls.isKeyMoversEmpty();
    }


    private static class ItemsMover<E> {
        private SelectionEventList<E> srcList;
        private SelectionEventList<E> destList;
        
        ItemsMover(SelectionEventList<E> pSourceList, SelectionEventList<E> pDestList) {
            srcList = pSourceList;
            destList = pDestList;
        }

        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("srList", srcList)
                    .add("destList", destList).toString();
        }

        void moveSelected() {
            if (srcList.isSelectionEmpty()) return;

            log.finer("before:\n\tsrcList=%s\n\tdestList=%s", srcList, destList);

            destList.addAll(srcList.removeSelected());

            log.finer("after:\n\tsrcList=%s\n\tdestList=%s", srcList, destList);
        }

        void moveAll() {
            if (srcList.isEmpty()) return;

            log.finer("before:\n\tsrcList=%s\n\tdestList=%s", srcList, destList);

            destList.addAll(srcList);
            srcList.clear();

            log.finer("after:\n\tsrcList=%s\n\tdestList=%s", srcList, destList);
        }
    }

    private static class MoveSelectedActionListener<E> implements ActionListener {
        private ItemsMover<E> itemsMover;

        MoveSelectedActionListener(ItemsMover<E> pItemsMover) {
            itemsMover = pItemsMover;
        }

        public void actionPerformed(ActionEvent pEvent) {
            itemsMover.moveSelected();
        }
    }

    private static class MoveSelectedKeyListener<E> extends KeyAdapter {
        private ItemsMover<E> itemsMover;
        private KeyStroke moveKeyStroke;

        MoveSelectedKeyListener(ItemsMover<E> pItemsMover, KeyStroke pMoveKeyStroke) {
            itemsMover = pItemsMover;
            moveKeyStroke = pMoveKeyStroke;
        }

        @Override
        public void keyPressed(KeyEvent pEvent) {
            log.finest("received keyEvent. keyStroke=", KeyStroke.getKeyStrokeForEvent(pEvent));
            
            if (!KeyStroke.getKeyStrokeForEvent(pEvent).equals(moveKeyStroke)) return;
            itemsMover.moveSelected();
        }

        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("itemsMover", itemsMover)
                    .add("moveKeyStroke", moveKeyStroke).toString();
        }
    }

    private static class MoveAllActionListener<E> implements ActionListener {
        private ItemsMover<E> itemsMover;

        MoveAllActionListener(ItemsMover<E> pItemsMover) {
            itemsMover = pItemsMover;
        }

        public void actionPerformed(ActionEvent pEvent) {
            itemsMover.moveAll();
        }
    }

    private static class MoveAllKeyListener<E> extends KeyAdapter {
        private ItemsMover<E> itemsMover;
        private KeyStroke moveKeyStroke;

        MoveAllKeyListener(ItemsMover<E> pItemsMover, KeyStroke pMoveKeyStroke) {
            itemsMover = pItemsMover;
            moveKeyStroke = pMoveKeyStroke;
        }

        @Override
        public void keyPressed(KeyEvent pEvent) {
            log.finest("received keyEvent. keyStroke=", KeyStroke.getKeyStrokeForEvent(pEvent));
            
            if (!KeyStroke.getKeyStrokeForEvent(pEvent).equals(moveKeyStroke)) return;
            itemsMover.moveAll();
        }

        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("itemsMover", itemsMover)
                    .add("moveKeyStroke", moveKeyStroke).toString();
        }
    }

    private static class ItemsTransferHandler<E> extends TransferHandler {
        private Transferable itemsMoverTransferable;
        private ItemsMover<E> itemsMover;

        private ItemsTransferHandler(ItemsMover<E> pItemsMover) {
            itemsMover = pItemsMover;
            itemsMoverTransferable = new ItemsMoverTransferable<E>(itemsMover);
        }

        @Override
        public int getSourceActions(JComponent pComponent) {
            log.fine("pComponent=", pComponent);
            return TransferHandler.MOVE;
        }

        @Override
        public boolean canImport(TransferSupport pSupport) {
            if (!pSupport.isDrop()) {
                log.fine("not a drop operation");
                return false;
            }

            if (!pSupport.isDataFlavorSupported(ItemsMoverTransferable.ITEMS_MOVER_DATA_FLAVOR)) {
                log.fine("itemsMoverDataFlavor not supported");
                return false;
            }

            if (itemsMover.equals(getItemsMoverTransferData(pSupport))) {
                log.fine("drop target same as drag source");
                return false;
            }

            return pSupport.getDropAction() == TransferHandler.MOVE;
        }

        @Override
        public boolean importData(TransferSupport pSupport) {
            // Need to call canImport here as well since a paste operation doesn't call it first
            if (!canImport(pSupport)) {
                log.fine("cannot import");
                return false;
            }

            ItemsMover<E> tItemsMover = getItemsMoverTransferData(pSupport);
            if (tItemsMover == null) return false;
            
            tItemsMover.moveSelected();

            return true;
        }
        
        @Override
        protected Transferable createTransferable(JComponent pComponent) {
            return itemsMoverTransferable;
        }

        @SuppressWarnings("unchecked")
        private ItemsMover<E> getItemsMoverTransferData(TransferSupport pSupport) {
            Transferable lTransferable = pSupport.getTransferable();
            ItemsMover<E> tItemsMover = null;
            try {
                tItemsMover = (ItemsMover<E>) lTransferable.getTransferData(
                        ItemsMoverTransferable.ITEMS_MOVER_DATA_FLAVOR);
            } catch (UnsupportedFlavorException ufe) {
                log.warning("itemsMoverDataFlavor not supported. Return null");
            } catch (IOException ioe) {
                log.warning("The ItemsMover transfer data is not available for the itemsMoverDataFlavor. Return null");
            }

            return tItemsMover;
        }
    }

    private static class ItemsMoverTransferable<E> implements Transferable {
        private ItemsMover<E> itemsMover;
        static final DataFlavor ITEMS_MOVER_DATA_FLAVOR;

        static {
            String itemsMoverMimeType = new StringBuilder(DataFlavor.javaJVMLocalObjectMimeType)
                    .append(";class=").append(ItemsMover.class.getName()).toString();

            try {
                ITEMS_MOVER_DATA_FLAVOR = new DataFlavor(itemsMoverMimeType);
            } catch (ClassNotFoundException cnfe) {
                throw new RuntimeException(cnfe);
            }
        }

        private ItemsMoverTransferable(ItemsMover<E> pItemsMover) {
            itemsMover = pItemsMover;
        }

        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[] {ITEMS_MOVER_DATA_FLAVOR};
        }

        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return ITEMS_MOVER_DATA_FLAVOR.equals(flavor);
        }

        public Object getTransferData(DataFlavor flavor) {
            if (ITEMS_MOVER_DATA_FLAVOR.equals(flavor)) return itemsMover;
            else return null;
        }
    }

    private static class ButtonsEnablerController<E> implements ListEventListener<E>,
            ListSelectionListener {
        private MoveControls<JButton> moveControls = new MoveControls<JButton>();


        public void listChanged(ListEvent<E> pEvent) {
            log.finest("pEvent=%s", pEvent);

            boolean isListEmpty = pEvent.getSourceList().isEmpty();

            // list changes (add, remove) only affect the move selected button when the list is empty
            if (isListEmpty && moveControls.moveSelected != null) {
                moveControls.moveSelected.setEnabled(false);
            }
            
            if (moveControls.moveAll != null) moveControls.moveAll.setEnabled(!isListEmpty);
        }

        public void valueChanged(ListSelectionEvent pEvent) {
            log.finest(String.format("pEvent=%s", pEvent));
            if (pEvent.getValueIsAdjusting()) return;

            JList list = (JList) pEvent.getSource();
            if (moveControls.moveSelected != null) {
                moveControls.moveSelected.setEnabled(!list.isSelectionEmpty());
            }
        }
 
        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("moveControls", moveControls).toString();
        }

        void setMoveSelectedButton(JButton pMoveSelectedButton) {
            moveControls.moveSelected = pMoveSelectedButton;
        }

        void setMoveAllButton(JButton pMoveAllButton) {
            moveControls.moveAll = pMoveAllButton;
        }
    }

    /**
     * This class groups two controls: the one that is used to move selected items and the one that
     * moves all the items.
     */
    private static class MoveControls<T> {
        private T moveSelected;
        private T moveAll;

        MoveControls() {}

        MoveControls(T pMoveSelected, T pMoveAll) {
            moveSelected = pMoveSelected;
            moveAll = pMoveAll;
        }
        
        @Override
        public String toString() {
            return Objects.toStringHelper(this).add("moveSelected", moveSelected)
                    .add("moveAll", moveAll).toString();
        }
        
        boolean isNotEmpty() {
            return !isEmpty();
        }

        boolean isEmpty() {
            return moveSelected == null && moveAll == null;
        }
    }

    private static class ListWidgetAndMoveControls<E> {
        private JList listWidget;
        private MoveControls<JButton> buttonMovers = new MoveControls<JButton>();
        private MoveControls<KeyStroke> keyMovers = new MoveControls<KeyStroke>();
        private MoveControls<KeyStroke> defaultKeyMovers;
        private SelectionEventList<E> selectionList;

        ListWidgetAndMoveControls(JList pListWidget, MoveControls<KeyStroke> pDefaultKeyMovers) {
            listWidget = pListWidget;
            defaultKeyMovers = pDefaultKeyMovers;
        }

        SelectionEventList<E> getSelectionEventList() {
            if (selectionList == null) selectionList = new SelectionEventList<E>(listWidget);
            return selectionList;
        }

        void setMoveSelectedButton(JButton pButton) {
            buttonMovers.moveSelected = pButton;
        }

        void setMoveAllButton(JButton pButton) {
            buttonMovers.moveAll = pButton;
        }

        JButton getMoveSelectedButton() {
            return buttonMovers.moveSelected;
        }

        JButton getMoveAllButton() {
            return buttonMovers.moveAll;
        }

        boolean isButtonMoversNotEmpty() {
            return buttonMovers.isNotEmpty();
        }

        KeyStroke getMoveSelectedKey() {
            return keyMovers.moveSelected;
        }

        KeyStroke getMoveAllKey() {
            return keyMovers.moveAll;
        }

        void setMoveSelectedKey(KeyStroke pKeyStroke) {
            KeyStroke tMoveAllKey = keyMovers.moveAll;
            Preconditions.checkArgument(
                    tMoveAllKey == null || !tMoveAllKey.equals(pKeyStroke),
                    "illegal key assignment");
            keyMovers.moveSelected = pKeyStroke;
        }

        void setMoveAllKey(KeyStroke pKeyStroke) {
            KeyStroke tMoveSelectedKey = keyMovers.moveSelected;
            Preconditions.checkArgument(
                    tMoveSelectedKey == null || !tMoveSelectedKey.equals(pKeyStroke),
                    "illegal key assignment");
            keyMovers.moveAll = pKeyStroke;
        }

        boolean isKeyMoversEmpty() {
            return keyMovers.isEmpty();
        }

        KeyStroke getDefaultMoveSelectedKey() {
            return defaultKeyMovers.moveSelected;
        }

        KeyStroke getDefaultMoveAllKey() {
            return defaultKeyMovers.moveAll;
        }
    }
}